package com.nx.platform.es.bean.modle.query;

import com.google.common.base.Enums;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.primitives.Floats;
import com.nx.platform.es.biz.wrapper.parser.StatementOperator;
import com.nx.platform.es.bean.dto.RequestContext;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.common.Strings;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.MultiMatchQueryBuilder.Type;

import java.util.List;
import java.util.Objects;

/**
 * @author
 * @since 2016年10月15日
 */
public class StringMultiMatchHandler implements QueryFieldHandler {

    @Override
    public void handle(ImmutableMap<String, ?> fieldConfig, RequestContext context, String fieldFace,
                       StatementOperator operator, String fieldValue, ListMultimap<Boolean, QueryBuilder> queryBuilders) {
        //
        if (Strings.isNullOrEmpty(fieldValue)) {
            return;
        }
        // 记录Query信息(Trace)
        context.getTrace().append("&").append(fieldFace).append(operator.getSymbol()).append(fieldValue);
        //
        String face = MapUtils.getString(fieldConfig, "face");
        Object _fields = MapUtils.getObject(fieldConfig, "_fields");
        String _type = MapUtils.getString(fieldConfig, "_type", Type.BEST_FIELDS.toString());
        String _operator = MapUtils.getString(fieldConfig, "_operator");
        String _minimum_should_match = MapUtils.getString(fieldConfig, "_minimum_should_match");
        String _analyzer = MapUtils.getString(fieldConfig, "_analyzer");
        float _tie_breaker = MapUtils.getFloatValue(fieldConfig, "_tie_breaker", 0f);
        int alternatives = MapUtils.getIntValue(fieldConfig, "_alternatives", 8);

        Preconditions.checkState(_fields instanceof List, "param _fields incorrect");
        List<?> fields = List.class.cast(_fields);
        String[] fieldNames = fields.stream().filter(Objects::nonNull).map(String::valueOf).toArray(String[]::new);
        if (fieldNames.length == 0) {
            return;
        }

        // if face 包含 cross 启用 cross mode
        if (face.equalsIgnoreCase("fulltext_cross")) {
            _type = Type.CROSS_FIELDS.toString();
        }

        // 短语模式 或者 启用了且 或者 使用了匹配度，使用 | 分割关键词
        if ("phrase".equals(_type) || "and".equals(_operator) || !Strings.isNullOrEmpty(_minimum_should_match)) {
            List<String> words = Splitter.on("|").omitEmptyStrings().splitToList(fieldValue);
            if (words.isEmpty()) {
                return;
            }
            if (words.size() == 1) {
                queryBuilders.put(operator.isSign(),
                        build(fieldNames, _type, _operator, _minimum_should_match, _analyzer, _tie_breaker,
                                fieldValue));
            } else if (words.size() <= alternatives) {
                BoolQueryBuilder bool = QueryBuilders.boolQuery();
                for (String word : words) {
                    bool.should(
                            build(fieldNames, _type, _operator, _minimum_should_match, _analyzer, _tie_breaker, word));
                }
                bool.minimumShouldMatch("1");
                queryBuilders.put(operator.isSign(), bool);
            } else {
                throw new IllegalArgumentException("too more alternatives: field=" + face + ", value=" + fieldValue);
            }
        } else {
            queryBuilders.put(operator.isSign(),
                    build(fieldNames, _type, _operator, _minimum_should_match, _analyzer, _tie_breaker, fieldValue));
        }
    }

    private MultiMatchQueryBuilder build(String[] fieldNames, String _type, String _operator,
            String _minimum_should_match, String _analyzer, float _tie_breaker, String fieldValue) {
        MultiMatchQueryBuilder multiMatch = QueryBuilders.multiMatchQuery(fieldValue);
        for (String fieldName : fieldNames) {
            int index = fieldName.indexOf('^');
            if (index > 0) {
                String field = fieldName.substring(0, index);
                Float boost = Floats.tryParse(fieldName.substring(index + 1));
                multiMatch.field(field, boost == null ? 1.0f : boost);
            } else {
                multiMatch.field(fieldName);
            }
        }
        multiMatch.type(Enums.getIfPresent(Type.class, _type.toUpperCase()).or(Type.BEST_FIELDS));
        if (!Strings.isNullOrEmpty(_analyzer)) {
            multiMatch.analyzer(_analyzer);
        }
        if ("and".equals(_operator)) {
            multiMatch.operator(Operator.AND);
        } else {
            multiMatch.operator(Operator.OR);
        }
        if (!Strings.isNullOrEmpty(_minimum_should_match)) {
            multiMatch.minimumShouldMatch(_minimum_should_match);
        } else {
            multiMatch.minimumShouldMatch("1");
        }
        if (_tie_breaker > 0f && _tie_breaker <= 1.0f) {
            multiMatch.tieBreaker(_tie_breaker);
        }
        return multiMatch;
    }

}
